<?php

include_once '../php/db_ini.php';
include_once '../php/metier/SparqlClient.php';
include_once '../php/metier/CarryUtils.php';

/**
 * Description of Sercher
 *
 * @author peyet
 */
class Searcher {
	private $db;
	private $sparql;
	private $stopWord;
	
	public function __construct() {
		$this->db = getConnexion();
		$this->sparql = new SparqlClient();
		$this->sparql->addPrefix("rdf:", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
		$this->sparql->addPrefix("rdfs:", "http://www.w3.org/2000/01/rdf-schema#");
		$this->sparql->addPrefix("owl:", "http://www.w3.org/2002/07/owl#");
		$this->sparql->addPrefix("xsd:", "http://www.w3.org/2001/XMLSchema#");
		$this->sparql->addPrefix("fn:", "http://www.w3.org/2005/xpath-functions#");
		$this->sparql->addPrefix(":", "http://www.irit.fr/recherches/MELODI/ontologies/FilmographieV1.owl#");
		
		$this->stopWord = $this->getStopWord();
	}
	
	public function search($requete) {
		$requete = strtolower($requete);
//		echo "===============================<br />";
//		echo "$requete <br />";
//		echo "===============================<br />";
		
		// On récupère la liste des termes non-vides de la requête
		$liste =  $this->getListeTermes($requete);
		
		// On complète la requête
		$liste_termes_inf = $this->addInferedTerms($liste);
		
		$str_termes = join(" ", $liste_termes_inf);
		$flat_list = split(" ", $str_termes);
		
		// On supprime les doublons
		$liste_termes = array_unique($flat_list);
		
//		echo "====> Resultat : " . join(", ", $liste_termes) . "<br />";
		
		// On récupère la liste des documents considérés comme pertinants par
		// rapport à la requête
		$liste_documents = $this->getListDocuments($liste_termes);
		
		// On récupère les rappels et précisions
		$rappel = $this->getRappels($requete, $liste_documents);
		$rappel5 = $this->getRappels($requete, $liste_documents, 5);
		$rappel10 = $this->getRappels($requete, $liste_documents, 10);
		$rappel25 = $this->getRappels($requete, $liste_documents, 25);
		$precision = $this->getPrecision($requete, $liste_documents);
		$precision5 = $this->getPrecision($requete, $liste_documents, 5);
		$precision10 = $this->getPrecision($requete, $liste_documents, 10);
		$precision25 = $this->getPrecision($requete, $liste_documents, 25);
		
		// On calcule les moyenne des rappels et des précisions
		$moyenneRappel = round ( ($rappel + $rappel5 + $rappel10 + $rappel25) / 4, 3 );
		$moyennePrecision = round ( ($precision + $precision5 + $precision10 + $precision25) / 4, 3 );
				
		$reponse = array(
			"id" => join("_", $liste_termes),
			"query" => join(", ", $liste_termes),
			"documents" => $liste_documents,
			"rappel" => $rappel,
			"rappel5" => $rappel5,
			"rappel10" => $rappel10,
			"rappel25" => $rappel25,
			"moyenneRappel" => $moyenneRappel,
			"precision" => $precision,
			"precision5" => $precision5,
			"precision10" => $precision10,
			"precision25" => $precision25,
			"moyennePrecision" => $moyennePrecision

		);
		
		return $reponse;
	}
	
	/**
	 * retourne la liste des termes non-vide radicalisés contenus dans la requête
	 * 
	 * @param String $requete
	 * @return array
	 */
	private function getListeTermes($requete) {
		// On parcours tous les mots de la requête
		$listeTermes = array();
		$eachElement = array();
		
		if (preg_match_all('/"(?:\\\\.|[^\\\\"])*"|\S+/', $requete, $eachElement)) {
			$eachElement = $eachElement[0];
		}
		
		$eachElement = array_map(function ($elem) { return str_replace("\"", "", $elem); }, $eachElement);
		
		foreach ($eachElement as $value){
			if(in_array($value, $this->stopWord) !== TRUE) {
				// Si le mot n'est pas un mot-vide, on l'insère son lemme
				$listeTermes[] = $this->getRadical($value);
			}
		}
		
		return $listeTermes;
	}
	
	private function addInferedTerms($liste_termes) {
		$complement = array();
		
		$properties = array();
		$ressources = array();
		
		foreach ($liste_termes as $terme) {
//			echo "*******************************************************<br />";
//			echo $terme . "<br />";
			
			// On construit le filtre de la requête
			$terme_splited = split(" ", $terme);
			$filter = "lang(?label) = \"fr\"";
			foreach ($terme_splited as $term_elem) {
				$filter .= " && fn:contains(?label, \"$term_elem\")";
			}
			
			// On assemble la requête
			$requete = "SELECT ?res ?label "
					 . "WHERE {"
					 . "	?res rdfs:label ?label."
					 . "	FILTER ( $filter )"
					 . "}";
			
			// On exécute la requête et on sélectionne les meilleurs résultats
			$res = $this->sparql->query($requete);
			$bestRessources = $this->filterBestRessources($terme, $res);
			
			// Pour chaque ressources repérées, on complète la requête
			foreach ($bestRessources as $elem) {
				$ressourceUri = $elem["ressource"];
				$isProperty = $this->isProperty($ressourceUri); 
				
				// On récupère les synonymes
				$synonymes = $this->getSynonymes($ressourceUri);
				$complement = array_merge($complement, $synonymes);
				
				// On récupère les supertypes
				$superstypes = $this->getSuperTypes($ressourceUri);
				$complement = array_merge($complement, $superstypes);
				
				// On récupère les superclasses
				$supersclass = $this->getSuperTypes($ressourceUri);
				$complement = array_merge($complement, $supersclass);
				
				// On récupère les labels des sous-classes
				$subclasses = $this->getSubclassesLabels($ressourceUri);
//				$complement = array_merge($complement, $subclasses);
				
				if ($isProperty) {
					array_push($properties, $ressourceUri);
					array_merge($properties, $supersclass);
					array_merge($properties, $subclasses);
				} else {
					array_push($ressources, $ressourceUri);
					array_merge($ressources, $supersclass);
					array_merge($ressources, $subclasses);
				}
			}
		}
		
		$inferedTerms = array_merge($liste_termes, $complement);
		
		$addings = $this->combineElems($ressources, $properties);
		$inferedTerms = array_merge($inferedTerms, $addings);
		
		return $inferedTerms;
	}
	
	private function combineElems($ressources, $properties) {
		$labels = array();
		foreach ($ressources as $ressourceURI) {
			foreach ($properties as $propertyURI) {
				$requete = "SELECT ?object ?label "
						. "WHERE {"
						. "	<$ressourceURI> <$propertyURI> ?object."
						. "	?object rdfs:label ?label."
						. "}";
				
				$res = $this->sparql->query($requete);
				
				foreach ($res->getBindings() as $binding) {
					array_push($labels, $binding["label"]->getValue());
				}
			}
		}
		
		return $labels;
	}
	
	private function isProperty($ressourceUri) {
		$requete = "SELECT ?type 
			WHERE {
				<$ressourceUri> rdf:type ?type.
			}";
		
		$res = $this->sparql->query($requete);
		$isProperty = false;
		
		foreach ($res->getBindings() as $binding) {
			$type = $binding["type"]->getValue();
			
			if (strpos($type, "Property") !== FALSE && 
					$this->sparql->hasPrefix($ressourceUri, ":")) {
				$isProperty = true;
			}
		}
		return $isProperty;
	}
	
	public function getSuperTypes($ressourceURI) {
		$requete = "SELECT ?label
			WHERE {
				<$ressourceURI> rdf:type ?type.
				?type rdfs:label ?label.
				FILTER (lang(?label) = \"fr\")
			}";
		
		$res = $this->sparql->query($requete);
		
		$labels = array();
		
		foreach ($res->getBindings() as $binding) {
			$label = $binding["label"]->getValue();
			array_push($labels, $this->getRadical($label));
		}
		
		return $labels;
	}
	
	public function getSuperClasses($ressourceURI) {
		$requete = "SELECT ?label
			WHERE {
				<$ressourceURI> rdfs:subClassOf ?type.
				?type rdfs:label ?label.
				FILTER (lang(?label) = \"fr\")
			}";
		
		$res = $this->sparql->query($requete);
		
		$labels = array();
		
		foreach ($res->getBindings() as $binding) {
			$label = $binding["label"]->getValue();
			array_push($labels, $this->getRadical($label));
		}
		
		return $labels;
	}
	
	private function getSubclassesLabels($ressourceUri) {
		// On récupère les types directs
		$requete = "SELECT ?directType 
			WHERE {
				<$ressourceUri> rdf:type ?directType.
				FILTER NOT EXISTS {
					<$ressourceUri> rdf:type ?type.
					?type rdfs:subClassOf ?directType.
					FILTER NOT EXISTS {
						?type owl:equivalentClass ?directType.
					}
				}
			}";
		
		$res = $this->sparql->query($requete);
		
		$filmOntoTypes = array();
		
		if ($this->sparql->hasPrefix($ressourceUri, ":")) {
			array_push($filmOntoTypes, $ressourceUri);
		}
		
		foreach ($res->getBindings() as $binding) {
			$type = $binding["directType"]->getValue();
			
			if ($this->sparql->hasPrefix($type, ":")) {
				array_push($filmOntoTypes, $type);
			}
		}
		
		// On récupère les labels des sous classes des types directs
		$labels = array();
		
		foreach ($filmOntoTypes as $type) {
			$requeteLabels = "SELECT distinct ?label "
					. "WHERE {"
					. "	?subClass rdfs:subClassOf <$type>."
					. "	?subClass rdfs:label ?label."
					. "	 FILTER( lang(?label) = \"fr\")"
					. "}";
			
			$resLabels = $this->sparql->query($requeteLabels);
			
			$labelsFr = array_map(function ($elem){
				return $elem["label"]->getValue();
			}, $resLabels->getBindings());
			
			$labels = array_merge($labels, $labelsFr);
		}
		
		return $labels;
	}
	
	private function getSynonymes($ressourceUri) {
		$requete = "SELECT ?label "
				 . "WHERE {"
				 . "	<$ressourceUri> rdfs:label ?label."
				 . "	FILTER( lang(?label) = \"fr\")"
				 . "}";
		
		$res = $this->sparql->query($requete);

		$labels = array();
		
		foreach ($res->getBindings() as $binding) {
			array_push($labels, $this->getRadical($binding["label"]->getValue()));
		}
		
		return $labels;
	}


	private function filterBestRessources($terme, $results) {
		$liste_labels = array();

		foreach ($results->getBindings() as $line) {
			$label = $line["label"]->getValue();
			similar_text($terme, $label, $similarite);
			if ($similarite > 60) {
				array_push($liste_labels, array(
					"ressource" => $line["res"]->getValue(),
					"label" => $label,
					"distance" => $similarite
				));
			}
		}
		
		return $liste_labels;
	}

	private function getRadical($chaine) {
		$liste = split(" +", $chaine);
		$chaine_finale = "";
		
		if (count($liste) > 1) {
			foreach ($liste as $mot) {
				if (in_array($mot, $this->stopWord) !== TRUE) {
					$chaine_finale .= $this->lemme($mot) . " ";
				}
			}
		} else {
			$chaine_finale = $this->lemme($chaine);
		}
		
		return trim($chaine_finale);
	}
	
	private function lemme($mot) {
		if (is_numeric($mot)) {
			return $mot;
		}
		
		$lemme = $mot;
		exec("cd /opt/tree-tagger-linux-3.2; "
				. "echo ".$mot." | "
				. "bin/tree-tagger -lemma -quiet -no-unknown /opt/tree-tagger-linux-3.2/lib/french-utf8.par 2>&1", $output);
		
		if (count($output) > 0) {
			$grpLemmes = preg_split("/\s+/", $output[0]);
			
			if (count($grpLemmes) == 2) {
				if (strpos("|", $grpLemmes[1]) === FALSE) {
					$lemme = $grpLemmes[1];
				} else {
					$grpLemmesSplit = split("|", $grpLemmes[1]);
					$lemme = $grpLemmesSplit[0];
				}
			}
		}
		
		return $lemme;
	}
	
	/**
	 * Tronque $mot à 7 caractères
	 * 
	 * @param String $mot
	 * @return String
	 */
	private function troncA7($mot) {
		$endIndex = min(strlen($mot), 7);

		if ($endIndex < 0) {
			$endIndex = 0;
		}

		return substr($mot, 0, $endIndex);
	}
	
	/**
	 * Récupère la liste des mots-vides stockée en base
	 * 
	 * @return array
	 */
	private function getStopWord() {
		$requete = "SELECT word FROM stop_word";
		$statement = $this->db->query($requete);
		$stopWords = array();

		while ($row = $statement->fetch()) {
			$stopWords[] = $row["word"];
		}

		return $stopWords;
	}
	
	/**
	 * Retourne la liste des documents jugés pertinents par rapport à la liste
	 * de mots-clefs passé en paramètre
	 * 
	 * @param array $keyWords
	 * @return array
	 */
	private function getListDocuments($keyWords) {
		// On construit la liste des mots clefs
		$keyWords = array_map(function ($elem) {
			return str_replace("'", "\\'", $elem);
		}, $keyWords);
		$keyWordsString = strtolower(
			"'" . join("', '", $keyWords). "'"
		);

		// On récupère les documents pertinents
		$requete = "SELECT 
						nom, SUM(poids) as sommePoids, GROUP_CONCAT(radical) AS radicaux
					FROM
						document d,
						occurence o,
						term t
					WHERE
						d.id = o.id_document AND t.id = o.id_term AND t.radical IN (" . $keyWordsString . ")
					GROUP BY nom
					ORDER BY sommePoids DESC
					LIMIT 50";
		
		$statement = $this->db->query($requete);
		$results = array();
		while ($row = $statement->fetch()) {
			// On vérifie la présence de tous les mots de la requête
//			$list_radicaux = split(",", $row["radicaux"]);
//			$nb_words = 0;
//			foreach ($keyWords as $word) {
//				if (in_array(strtolower($word), $list_radicaux)){
//					$nb_words++;
//				}
//			}
			$results[] = $row["nom"];
		}

		return $results;
	}

	private function getRappels($query, $liste_documents, $filtre = -1) {
		
		if($filtre == -1){
			$filtre = count($liste_documents);
		}

		$sublist = array_slice($liste_documents, 0, $filtre);

		$NombreTotalDocumentsPertinents = $this->getNombreTotalDocumentsPertinents($query);
		$nombreDocumentsPertinentsSelectione = $this->getNombreDocumentsPertinentsSelectionne($query, $sublist);
		if ($NombreTotalDocumentsPertinents == 0) {
			$rappel = 0;
		} else {
			$rappel = round ( $nombreDocumentsPertinentsSelectione / $NombreTotalDocumentsPertinents, 3 );
		}

		return $rappel;

	}

	private function getPrecision($requete, $liste_documents, $filtre = -1) {
		if ($filtre == -1) {
			$filtre = count($liste_documents);
		}
		
		$sublist = array_slice ($liste_documents , 0 , $filtre);
		
		$nombreDocumentsPertinentsSelectionnes = $this->getNombreDocumentsPertinentsSelectionne($requete, $sublist);
		$nombreDocumentsSelectionnes = count($sublist);
		
		$precision = 0;
		if ($nombreDocumentsSelectionnes == 0) {
			$precision = 0;
		} else {
			$precision = round ( $nombreDocumentsPertinentsSelectionnes / $nombreDocumentsSelectionnes, 3 );
		}
		
		return $precision;
	}
	
	private function getNombreTotalDocumentsPertinents($requete) {
		$requete = "SELECT 
						SUM(pertinence) nb 
					FROM 
						web_sem.requetes r, 
						web_sem.pertinences p 
					WHERE r.id = p.id_requete 
					  AND intitule LIKE '" . $requete . "'";

		$statement = $this->db->query($requete);
		$row = $statement->fetch();
		return $row["nb"];
	}

	private function getNombreDocumentsPertinentsSelectionne($requete, $liste_documents) {
		$liste_string = "'" . join("', '", $liste_documents) . "'";

		$requete = "SELECT 
						SUM(pertinence) nb 
					FROM 
						web_sem.requetes r, 
						web_sem.pertinences p 
					WHERE r.id = p.id_requete 
					  AND document IN (" . $liste_string . ") 
					  AND intitule LIKE '" . $requete . "'";
		
		$statement = $this->db->query($requete);
		$row = $statement->fetch();
		return $row["nb"];
	}
}
